package job;

import java.util.Date;

/**
* Repr�sente le fonctionnement du jeu Tetris
* @author Laurent et Flav
*/
public class Game {
	/** Pi�ce en cours de traitement, celle qui est jou�e par le joueur **/
	FallingPiece curPiece;
	/** Pi�ce suivante **/
	Piece nextPiece;
	/** Objet du type UserOption **/
	private UserOption userOption;
	/** Objet du type Score **/
	protected Score score;
	/** Le terrain de jeu */
	private PlayGround playGround;
	/** Game end : 0 pas fini, 1 fini */
	private boolean gameEnd;
	/** interface visuel */
	private Drawer tetrisDrawer;
	/** Le controleur **/
	private Controler tretrisControler;
	/** initialiser avec un initTimer() de l'interface userOption */
	private float timer;
	/** niveau courant */
	private int level=0;
	/** prochaine niveau en nb de lignes a atteindre */
	private int nextLevel;
	/**
	 * Constructeur.
	 * @param d le drawer est une interface pour l'affichage
	 * @param c est une iterface pour les méthodes d'input 
	 * @param uo défini une implémentation de l'interface d'option */
	public Game(Drawer d, Controler c, UserOption uo) {
		tretrisControler = c;
		tetrisDrawer = d;
		tetrisDrawer.setEventListener(c);
		userOption = uo;
		while(!tetrisDrawer.requestNewGame()) {
			
		}
		resetGame();
	}
	/**
	* Constructeur avec au minimum un drawer et un controler
	* Pas de userOption spécifié. Une implémentation par defaut
	* de l'interface est utilisé. Donc des options par défaut
	* sans parametrage possible.
	*/
	public Game(Drawer d, Controler c) {
		this(d, c, new DefaultUserOption());
	}
	
	/** Nouvelle partie */
	public void resetGame() {
		playGround = new PlayGround(userOption.getPlayGroundX(), userOption.getPlayGroundY());
		curPiece = new FallingPiece(randPiece());
		curPiece.randRot();
		curPiece.setPos(playGround.getWidth()/2 -2, playGround.getHeight()-1);
		
		nextPiece = randPiece();
		nextPiece.randRot();
		gameEnd = false;
		
		score = new Score();
		tetrisDrawer.setPlayGroundDimensions(playGround.getWidth(), playGround.getHeight());
		level = userOption.getInitLvl();
		timer = 500 - (level*userOption.getTimerPas());
		nextLevel = 5;
		start();
	}
	
	/** Demarre le mecanisme de jeu */
	public void start() {
		tetrisDrawer.drawScore(0, 0, level);
		while(!gameEnd) {
			this.mainLoop();
		}
		// on a perdu
		tetrisDrawer.drawGameOver();
		while(!tetrisDrawer.requestNewGame()) {
		}
		resetGame();
	}
	
	/** Boucle principale du jeu */
	private void mainLoop() {
		tetrisDrawer.clear();
		draw();
		Date now = new Date();
		while(new Date().getTime() - now.getTime() <= timer) {
			if(tretrisControler.leftMoveIsRequest()) {
				if(playGround.testLeft(curPiece)) {
					curPiece.left();
					tetrisDrawer.clear();
					draw();
				}
			}
			else if(tretrisControler.rightMoveIsRequest()) {
				if(playGround.testRight(curPiece)) {
					curPiece.right();
					tetrisDrawer.clear();
					draw();
				}
			}
			else if(tretrisControler.downMoveIsRequest()) {
				while(playGround.testDown(curPiece)) {
					curPiece.down();
				}
				tetrisDrawer.clear();
				draw();
			}
			else if(tretrisControler.turnMoveIsRequest()) {
				if(playGround.testRotateLeft(curPiece)) {
					curPiece.nextRot();
					tetrisDrawer.clear();
					draw();
				}
			}
			
			// relance la partie
			if(tetrisDrawer.requestNewGame())
				resetGame();
		}
		if(playGround.testDown(curPiece)) {
			curPiece.down();
		}
		else {
			int nl = playGround.deleteCompleteRows();
			score.addLines(nl);
			if(score.getNbLines()>=nextLevel)
				nextLvl();
			score.addPoints(10*nl*nl);
			tetrisDrawer.drawScore(score.getNbLines(), score.getNbPoints(), level);
			// on passe a la piece suivante
			curPiece = new FallingPiece(nextPiece);
			curPiece.setPos(playGround.getWidth()/2 -2, playGround.getHeight()-1);
			
			nextPiece = randPiece();
			nextPiece.randRot();
		}
		
		if(playGround.overflow())
			gameEnd = true;
	}
	
	/** change la vitesse du jeu */
	private void nextLvl() {
		level++;
		nextLevel += 5;
		timer -= userOption.getTimerPas();
	}
	
	/** requete de dessin de l'ensemble */
	private void draw() {
		drawPiece();
		drawRows();
		drawNextPiece();
	}
	
	/** requete de dessin les blocks de la falling piece */
	private void drawPiece() {
		for(int x=0; x<4; x++) {
			for(int y=0; y<4; y++) {
				if(!curPiece.isEmpty(x, y))
					tetrisDrawer.drawBlockAtPosition(curPiece.getHPos()+x, curPiece.getVPos()+y, curPiece.getBlock(x, y).getColor());
			}
		}
	}
	/** dessine les blocks de la piece suivante */
	private void drawNextPiece() {
		for(int x=0; x<4; x++) {
			for(int y=0; y<4; y++) {
				if(!nextPiece.isEmpty(x, y))
					tetrisDrawer.drawNextPieceBlock(x, y, nextPiece.getBlock(x, y).getColor());
			}
		}
	}
	/** dessine les blocks des rows */
	private void drawRows() {
		for(int i=0; i<playGround.rows.size(); i++) {
			for(int j=0; j<playGround.getWidth(); j++)
				if(!playGround.rows.get(i).isEmpty(j))
					tetrisDrawer.drawBlockAtPosition(j, i, playGround.rows.get(i).getBlock(j).getColor());
		}
	}
	/**
	* Cr�ation de la pi�ce de forme L
	* @return
	*/
	private Piece createLPiece() {
		int[] x0 = {0,1,2,2};
		int[] y0 = {0,0,0,1};
		RotPiece rot0 = new RotPiece(x0, y0, 0);
		int[] x1 = {1,1,1,2};
		int[] y1 = {0,1,2,0};
		RotPiece rot1 = new RotPiece(x1, y1, 0);
		int[] x2 = {0,0,1,2};
		int[] y2 = {0,1,1,1};
		RotPiece rot2 = new RotPiece(x2, y2, 0);
		int[] x3 = {0,1,1,1};
		int[] y3 = {2,2,1,0};
		RotPiece rot3 = new RotPiece(x3, y3, 0);
		rot0.setPrevRotAndNextRot(rot3, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot2);
		rot2.setPrevRotAndNextRot(rot1, rot3);
		rot3.setPrevRotAndNextRot(rot2, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme L invers�
	* @return
	*/
	private Piece createLInvPiece() {
		int[] x0 = {0,1,2,0};
		int[] y0 = {0,0,0,1};
		RotPiece rot0 = new RotPiece(x0, y0, 1);
		int[] x1 = {1,1,1,2};
		int[] y1 = {0,1,2,2};
		RotPiece rot1 = new RotPiece(x1, y1, 1);
		int[] x2 = {0,1,2,2};
		int[] y2 = {1,1,1,0};
		RotPiece rot2 = new RotPiece(x2, y2, 1);
		int[] x3 = {1,2,2,2};
		int[] y3 = {0,0,1,2};
		RotPiece rot3 = new RotPiece(x3, y3, 1);
		rot0.setPrevRotAndNextRot(rot3, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot2);
		rot2.setPrevRotAndNextRot(rot1, rot3);
		rot3.setPrevRotAndNextRot(rot2, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme N
	* @return
	*/
	private Piece createNPiece() {
		int[] x0 = {0,0,1,1};
		int[] y0 = {0,1,1,2};
		RotPiece rot0 = new RotPiece(x0, y0, 2);
		int[] x1 = {0,1,1,2};
		int[] y1 = {1,1,0,0};
		RotPiece rot1 = new RotPiece(x1, y1, 2);
		rot0.setPrevRotAndNextRot(rot1, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme N invers�
	* @return
	*/
	private Piece createNInvPiece() {
		int[] x0 = {0,0,1,1};
		int[] y0 = {1,2,1,0};
		RotPiece rot0 = new RotPiece(x0, y0, 3);
		int[] x1 = {0,1,1,2};
		int[] y1 = {0,0,1,1};
		RotPiece rot1 = new RotPiece(x1, y1, 3);
		rot0.setPrevRotAndNextRot(rot1, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme I
	* @return
	*/
	private Piece createIPiece() {
		int[] x0 = {2,2,2,2};
		int[] y0 = {0,1,2,3};
		RotPiece rot0 = new RotPiece(x0, y0, 4);
		int[] x1 = {0,1,2,3};
		int[] y1 = {0,0,0,0};
		RotPiece rot1 = new RotPiece(x1, y1, 4);
		rot0.setPrevRotAndNextRot(rot1, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme T
	* @return
	*/
	private Piece createTPiece() {
		int[] x0 = {0,1,1,2};
		int[] y0 = {0,0,1,0};
		RotPiece rot0 = new RotPiece(x0, y0, 5);
		int[] x1 = {1,1,1,2};
		int[] y1 = {0,1,2,1};
		RotPiece rot1 = new RotPiece(x1, y1, 5);
		int[] x2 = {0,1,1,2};
		int[] y2 = {1,1,0,1};
		RotPiece rot2 = new RotPiece(x2, y2, 5);
		int[] x3 = {0,1,1,1};
		int[] y3 = {1,0,1,2};
		RotPiece rot3 = new RotPiece(x3, y3, 5);
		rot0.setPrevRotAndNextRot(rot3, rot1);
		rot1.setPrevRotAndNextRot(rot0, rot2);
		rot2.setPrevRotAndNextRot(rot1, rot3);
		rot3.setPrevRotAndNextRot(rot2, rot0);
		
		return new Piece(rot0);
	}
	
	/**
	* Cr�ation de la pi�ce de forme carr�
	* @return
	*/
	private Piece createEdgePiece() {
		int[] x0 = {0,0,1,1};
		int[] y0 = {0,1,0,1};
		RotPiece rot0 = new RotPiece(x0, y0, 6);
		
		return new Piece(rot0);
	}
	
	/** Retourne une piece aleatoir parmis celles disponibles */
	private Piece randPiece() {
		int randInt = (int)(Math.random()*7);
		switch(randInt) {
		case 0:
			return createLPiece();
		case 1:
			return createLInvPiece();
		case 2:
			return createNPiece();
		case 3:
			return createNInvPiece();
		case 4:
			return createTPiece();
		case 5:
			return createIPiece();
		case 6:
			return createEdgePiece();
		}
		return null;
	}
}
